package ru.amse.tsyganov.jumleditor.view.common;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;

import ru.amse.tsyganov.jumleditor.commands.Command;
import ru.amse.tsyganov.jumleditor.commands.CommandFactory;
import ru.amse.tsyganov.jumleditor.graphicaleditor.Coordinates;
import ru.amse.tsyganov.jumleditor.model.common.Edge;
import ru.amse.tsyganov.jumleditor.model.common.GraphModel;
import ru.amse.tsyganov.jumleditor.model.common.State;
import ru.amse.tsyganov.jumleditor.properties.StringProperty;
import ru.amse.tsyganov.jumleditor.view.ConnectionPoint;
import ru.amse.tsyganov.jumleditor.view.ViewVisitor;
import ru.amse.tsyganov.jumleditor.view.ConnectionPoint.ConnectionPointType;
import ru.amse.tsyganov.jumleditor.view.activepoints.ActivePoint;
import ru.amse.tsyganov.jumleditor.view.activepoints.NominalActivePoint;
import ru.amse.tsyganov.jumleditor.view.activepoints.ResizeActivePoint;
import ru.amse.tsyganov.jumleditor.view.preferences.Colors;
import ru.amse.tsyganov.jumleditor.view.preferences.Cursors;

public abstract class StateView<T extends State> extends ViewElement<T> {
		
	private final GraphView<? extends GraphModel> graphView;
	
	private int stateViewX;
	
	private int stateViewY;
	
	private int stateViewWidth = 80;
	
	private int stateViewHeight = 40;
	
	private final List<ActivePoint> activePoints;
	
	private final List<ConnectionPoint> connectionPoints;
	
	private final StringProperty mainProperty = new StringProperty("Name") {

		@Override
		public String getPropertyValue() {
			return getModel().getName();
		}

		@Override
		public void setPropertyValue(String newValue) {
			getModel().setName(newValue);
		}
	
	};
		
	public StateView(T model,
			GraphView<? extends GraphModel> activityGraphView) {
		
		super(model);
		activePoints = createActivePoints();
		connectionPoints = createConnectionPoints();
		this.graphView = activityGraphView;
		initProperties();
	}
	
	public int getX() {
		return stateViewX;
	}
	
	public void setX(int x) {
		this.stateViewX =x;
	}
	
	public int getY() {
		return stateViewY;
	}
	
	public void setY(int y) {
		this.stateViewY = y;
	}
	
	public int getWidth() {
		return stateViewWidth;
	}
	
	public void setWidth(int newWidth) {
		if (newWidth < 0) {
			throw new IllegalArgumentException();
		}
		
		this.stateViewWidth = newWidth;
	}
	
	public int getHeight() {
		return stateViewHeight;
	}
	
	public void setHeight(int newHeight) {
		if (newHeight < 0) {
			throw new IllegalArgumentException();
		}
		
		this.stateViewHeight = newHeight;
	}
	
	@Override
	public Point getTextOutputPoint() {
		return new Point(getX() + getWidth()/2, getY() + getHeight()/2);
	}
	
	public Rectangle getRectangle() {
		return new Rectangle(stateViewX, stateViewY, stateViewWidth, stateViewHeight);
	}
	
	public Rectangle getAttractiveRectangle() {
		int margin = 10;
		return new Rectangle(stateViewX - margin, stateViewY - margin,
				stateViewWidth + 2 * margin, stateViewHeight + 2 * margin);
	}
	
	@Override
	public void accept(ViewVisitor v) {
		v.visitStateView(this);
		visitTransitions(v);
	}
	
	protected void visitTransitions(ViewVisitor v) {
		for (Edge t : getModel().getStateVertex().getOutgoing()) {
			EdgeView<? extends Edge> transitionView = 
				getModelViewMap().getEdgesMap().get(t);
			transitionView.accept(v);
		}
	}
	
	@Override
	public final void paint(GC gc, Coordinates coordinates) {
		assert gc != null;
		
		paintFigure(gc, coordinates);
		
		if (getSelectedItems().selectedStatesContains(this)) {
			
			gc.setForeground(Colors.getBlackColor());
			gc.setLineStyle(SWT.LINE_DOT);
			
			final int MARGIN = 2;
			
			gc.drawRectangle(
					coordinates.fromModelXToScreenX(getX() - MARGIN),
					coordinates.fromModelYToScreenY(getY() - MARGIN),
					getWidth() + 2 * MARGIN,
					getHeight() + 2 * MARGIN);
			
			gc.setLineStyle(SWT.LINE_SOLID);
		}		
	}
	
	public void paintTransitions(GC gc, Coordinates coordinates) {
		for (Edge t : getModel().getStateVertex().getOutgoing()) {
			EdgeView<? extends Edge> transitionView = 
				getModelViewMap().getEdgesMap().get(t);
			transitionView.paint(gc, coordinates);
		}
	}
	
	@Override
	public void prepaintEvent(GC gc, Coordinates coordinates) {
		if (getX() + getWidth() > coordinates.getMaxX()) {
			coordinates.setMaxX(getX() + getWidth());
		}
		
		if (getY() + getHeight() > coordinates.getMaxY()) {
			coordinates.setMaxY(getY() + getHeight());
		}
		
		if (getX() < coordinates.getMinX()) {
			coordinates.setMinX(getX());
		}
		
		if (getY() < coordinates.getMinY()) {
			coordinates.setMinY(getY());
		}
		for (Edge t : getModel().getStateVertex().getOutgoing()) {
			getActivityGraphView().getModelViewMap().getEdgesMap().get(t).prepaintEvent(gc, coordinates);
		}
	}
	
	public List<ConnectionPoint> getConnectionPoints() {
		return connectionPoints;
	}
	
	@Override
	public List<ActivePoint> getActivePoints() {
		if (getSelectedItems().selectedStatesContains(this)) {
			return activePoints;
		} else {
			return Collections.emptyList();
		}
	}

	@Override
	public GraphView<? extends GraphModel> getActivityGraphView() {
		return graphView;
	}
	
	public abstract void paintFigure(GC gc, Coordinates coordinates);
	
	@Override
	public void paintActivePoints(GC gc, Coordinates coordinates) {
		if (getSelectedItems().selectedStatesContains(this)) {
			for (ActivePoint p : getActivePoints()) {
				p.paintActivePoint(gc, coordinates);
			}
		}
	}
	
	public void paintConnectionPoints(GC gc, Coordinates coordinates) {
		for (ConnectionPoint p : connectionPoints) {
			p.paint(gc, coordinates);
		}
	}
	
	private void initProperties() {
		super.getProperties().addProperty(mainProperty);
	}
	
	@Override
	public StringProperty getMainProperty() {
		return mainProperty;
	}
	
	public ConnectionPoint getNearestConnectionPoint(int x, int y) {
		int len = Integer.MAX_VALUE;
		ConnectionPoint nearestPoint = null;
		for (ConnectionPoint p : connectionPoints) {
			int currLen = (int) (Math.pow(p.getCpX() - x, 2) +
				Math.pow(p.getCpY() - y, 2));
			
			if (currLen < len) {
				len = currLen;
				nearestPoint = p;
			}
		}
		return nearestPoint;
	}
	
	public List<ConnectionPoint> createConnectionPoints() {
		List<ConnectionPoint> points = new ArrayList<ConnectionPoint>(4);
				
		ConnectionPoint top = new ConnectionPoint(ConnectionPointType.TOP, this, 0) {

			@Override
			public int getCpX() {
				return getX() + getWidth() / 2;
			}

			@Override
			public int getCpY() {
				return getY();
			}
			
		};
		
		ConnectionPoint right = new ConnectionPoint(ConnectionPointType.RIGHT, this, 1) {

			@Override
			public int getCpX() {
				return getX() + getWidth();
			}

			@Override
			public int getCpY() {
				return getY() + getHeight() / 2;
			}
			
		};
		
		ConnectionPoint bottom = new ConnectionPoint(ConnectionPointType.BOTTOM, this, 2) {

			@Override
			public int getCpX() {
				return getX() + getWidth() / 2;
			}

			@Override
			public int getCpY() {
				return getY() + getHeight();
			}
			
		};
		
		ConnectionPoint left = new ConnectionPoint(ConnectionPointType.LEFT, this, 3) {

			@Override
			public int getCpX() {
				return getX();
			}

			@Override
			public int getCpY() {
				return getY() + getHeight() / 2;
			}
			
		};
		
		points.add(top);
		points.add(right);
		points.add(bottom);
		points.add(left);
		
		return points;
	}
	
	private List<ActivePoint> createActivePoints() {
		ResizeActivePoint topLeftPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						getTemporaryActivePointX(), 
						getTemporaryActivePointY(), 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeNW();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth + stateViewX - getTemporaryActivePointX();
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight + stateViewY - getTemporaryActivePointY();
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(
						getTemporaryActivePointX(), 
						getTemporaryActivePointY(), 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint topRightPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						getTemporaryActivePointY(), 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeNE();
			}
			@Override
			public int getNewWidth() {
				return getTemporaryActivePointX() - stateViewX;
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight + stateViewY - getTemporaryActivePointY();
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(
						stateViewX, getTemporaryActivePointY(), 
						getNewWidth(), getNewHeight());
			}	
		};
		
		ResizeActivePoint bottomLeftPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						getTemporaryActivePointX(), 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeNE();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth + stateViewX - getTemporaryActivePointX();
			}
			@Override
			public int getNewHeight() {
				return getTemporaryActivePointY() - stateViewY;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(getTemporaryActivePointX(), stateViewY, 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint bottomRightPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeNW();
			}
			@Override
			public int getNewWidth() {
				return getTemporaryActivePointX() - stateViewX;
			}
			@Override
			public int getNewHeight() {
				return getTemporaryActivePointY() - stateViewY;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(stateViewX, stateViewY, 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint topCenterPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						getTemporaryActivePointY(), 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeN();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth;
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight + stateViewY - getTemporaryActivePointY();
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth / 2;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(
						stateViewX, 
						getTemporaryActivePointY(), 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint rightCenterPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeW();
			}
			@Override
			public int getNewWidth() {
				return getTemporaryActivePointX() - stateViewX;
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight / 2;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(
						stateViewX, stateViewY, 
						getNewWidth(), getNewHeight());
			}	
		};
		
		ResizeActivePoint bottomCenterPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeN();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth;
			}
			@Override
			public int getNewHeight() {
				return getTemporaryActivePointY() - stateViewY;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth / 2;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(stateViewX, stateViewY, 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint leftCenterPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						getTemporaryActivePointX(), 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeE();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth + stateViewX - getTemporaryActivePointX();
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight / 2;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(getTemporaryActivePointX(), stateViewY, 
						getNewWidth(), getNewHeight());
			}
		};
		
		List<ActivePoint> points = new ArrayList<ActivePoint>(8);
		points.add(topLeftPoint);
		points.add(topCenterPoint);
		points.add(topRightPoint);
		points.add(rightCenterPoint);
		points.add(bottomRightPoint);
		points.add(bottomCenterPoint);
		points.add(bottomLeftPoint);
		points.add(leftCenterPoint);
		return points;
	}

	protected List<ActivePoint> createNominalActivePoints() {
		NominalActivePoint topLeftPoint = new NominalActivePoint() {
			
			@Override
			public int getCurrentActivePointX() {
				return getX();
			}
			
			@Override
			public int getCurrentActivePointY() {
				return getY();
			}
		};
		
		NominalActivePoint topRightPoint = new NominalActivePoint() {
			
			@Override
			public int getCurrentActivePointX() {
				return getX() + getWidth();
			}
			
			@Override
			public int getCurrentActivePointY() {
				return getY();
			}
		};
		
		NominalActivePoint bottomLeftPoint = new NominalActivePoint() {

			@Override
			public int getCurrentActivePointX() {
				return getX();
			}
			
			@Override
			public int getCurrentActivePointY() {
				return getY() + getHeight();
			}
		};
		
		NominalActivePoint bottomRightPoint = new NominalActivePoint() {
			
			@Override
			public int getCurrentActivePointX() {
				return getX() + getWidth();
			}
			
			@Override
			public int getCurrentActivePointY() {
				return getY() + getHeight();
			}
		};
		
		List<ActivePoint> points = new ArrayList<ActivePoint>(8);
		points.add(topLeftPoint);
		points.add(topRightPoint);
		points.add(bottomRightPoint);
		points.add(bottomLeftPoint);
		return points;
	}
	
}
